﻿using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Plugins;
using StockAccessors;
using StockModel;
using System;

namespace TimeBarPlugin
{
	internal class DecisionEngine : IDisposable
	{
		#region Attributes

		private readonly EngineSettingsBase _settings;
		private readonly ObservableCollection<Quotation> _quotationSource;

		private readonly OperationManager _operationManager;
		private readonly VolumeCalculator _volumeCalculator;
		private readonly DynamicStopLossManager _dynamicStopLoss;

		private readonly object _stateMachineLock;

		private readonly IStockDataAccessorInformation _accessorInformation;


		#endregion

		#region Properties

		public DecisionEngineData Data { get; private set; }

		#endregion

		#region Events

		public event EventHandler<RealtimeNotificationEventArgs> RealtimeNotification;

		#endregion

		#region Public Methods

		// Constructor used by the historical engine without checking the connection status.
		public DecisionEngine(EngineSettingsBase settings, OperationManager operationManager, VolumeCalculator volumeCalculator, ObservableCollection<Quotation> quotationSource, DecisionEngineData data)
		{
			_settings = settings;
			_operationManager = operationManager;
			_volumeCalculator = volumeCalculator;
			_quotationSource = quotationSource;

			this.Data = data;
			this.Data.CurrentVolume = this.Data.CurrentVolume == 0 ? _settings.QuantityToBuy : this.Data.CurrentVolume;

			_stateMachineLock = new object();
			_dynamicStopLoss = new DynamicStopLossManager(_settings.StopLoss, _quotationSource);

			_quotationSource.CollectionChanged += QuotationSource_CollectionChanged; // Disposed

			if (_settings.DynamicStop)
			{
				_dynamicStopLoss.PropertyChanged += _dynamicStopLoss_PropertyChanged; // Disposed
			}
		}

		// Constructor used by the realtime engine to check the connection status.
		public DecisionEngine(EngineSettingsBase settings, OperationManager operationManager, VolumeCalculator volumeCalculator, ObservableCollection<Quotation> quotationSource, DecisionEngineData data, IStockDataAccessorInformation accessorInformation) : this(settings, operationManager, volumeCalculator, quotationSource, data)
		{
			_accessorInformation = accessorInformation;

			this.ManageConnection();

			_accessorInformation.PropertyChanged += AccessorInformation_PropertyChanged; // Disposed
		}

		protected virtual void OnRealtimeNotification(string message)
		{
			if (this.RealtimeNotification != null)
			{
				this.RealtimeNotification(this, new RealtimeNotificationEventArgs(message));
			}
		}

		#endregion

		#region Event Handlers

		private void QuotationSource_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
		{
			if (e.Action == NotifyCollectionChangedAction.Add)
			{
				this.Data.LastQuotation = (Quotation)e.NewItems[e.NewItems.Count - 1]; // Only take the last quotation received (we will receive always one as loaders do it one by one)
				this.EvaluateStateMachine();
				this.Data.ProcessedDay = this.Data.LastQuotation.Date.Date;
			}
		}

		private void _dynamicStopLoss_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			this.Data.StopLoss = _dynamicStopLoss.StopValue;
		}

		private void AccessorInformation_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			if (e.PropertyName == "Connected")
			{
				this.ManageConnection();
			}
		}

		#endregion

		#region Private Methods

		private void ManageConnection()
		{
			lock (_stateMachineLock)
			{
				if (_accessorInformation.Connected)
				{
					if (this.Data.PreviousState != DecisionEngineState.None)
					{
						this.Data.State = this.Data.PreviousState;
						this.OnRealtimeNotification("Reconnection detected. Engine on restarted. Current state: " + this.Data.PreviousState);
					}
					this.Data.PreviousState = DecisionEngineState.None;
				}
				else if (this.Data.State != DecisionEngineState.AccessorDisconnected)
				{
					this.Data.PreviousState = this.Data.State;
					this.Data.State = DecisionEngineState.AccessorDisconnected;
					this.OnRealtimeNotification("Disconnection detected. Engine on hold. Current state: " + this.Data.PreviousState);
				}
			}
		}

		private void EvaluateStateMachine()
		{
			lock (_stateMachineLock)
			{
				if (this.Data.State == DecisionEngineState.WaitingOpen)
				{
					if (this.Data.LastQuotation.Date.TimeOfDay > _settings.LastOperationStart)
					{
						this.Data.State = DecisionEngineState.DayClosed;
					}
					else if (this.Data.LastQuotation.Volume > 0)
					{
						if (_settings.ExecutionDirection == ExecutionDirection.Up)
						{
							// This has to be changed for a order on opening bid, before volume is greater than 0
							this.OpenLongPossition(this.Data.CurrentVolume);

							this.Data.StartValue = _operationManager.CurrentOperation.BuyPrice;
							this.Data.StopLoss = this.Data.StartValue - _settings.StopLoss;
						}
						else if (_settings.ExecutionDirection == ExecutionDirection.Down)
						{
							// This has to be changed for a order on opening bid, before volume is greater than 0
							this.OpenShortPossition(this.Data.CurrentVolume);

							this.Data.StartValue = _operationManager.CurrentOperation.SellPrice;
							this.Data.StopLoss = this.Data.StartValue + _settings.StopLoss;
						}
						else
						{
							if (this.Data.StartValue == 0) // Don't use real value if manually set
							{
								this.Data.StartValue = this.Data.LastQuotation.Value;	
							}
							
							this.Data.HighThreshold = this.Data.StartValue + _settings.StopLoss;
							this.Data.LowThreshold = this.Data.StartValue - _settings.StopLoss;

							this.Data.State = DecisionEngineState.WaitingThreshold;
						}

						this.Data.BlockStart = this.Data.LastQuotation.Date;
					}
				}

				this.CalculateMaxMin(); // It has to be calculated after the WaitingOpen to not lose the open quotation.

				if (this.Data.State == DecisionEngineState.WaitingThreshold)
				{
					if (this.Data.LastQuotation.Date.TimeOfDay > _settings.LastOperationStart)
					{
						this.Data.State = DecisionEngineState.DayClosed;
					}
					//else if (this.Data.LastQuotation.Sellers[0].Value >= this.Data.HighThreshold && this.Data.LastQuotation.Sellers[0].Quantity > this.Data.CurrentVolume)
					else if (this.Data.LastQuotation.Value >= this.Data.HighThreshold && this.Data.LastQuotation.Sellers[0].Quantity > this.Data.CurrentVolume)
					{
						if (_settings.MaxInverseValue != -1 && Math.Abs(this.Data.Min) > _settings.MaxInverseValue)
						{
							this.Data.State = DecisionEngineState.WaitingNewBlock;							
						}
						else
						{
							this.OpenLongPossition(this.Data.CurrentVolume);
							this.Data.StopLoss = _settings.StopLossAt0 ? this.Data.StartValue : this.Data.LowThreshold;
						}
					}
					//else if (this.Data.LastQuotation.Buyers[0].Value <= this.Data.LowThreshold && this.Data.LastQuotation.Sellers[0].Quantity > this.Data.CurrentVolume)
					else if (this.Data.LastQuotation.Value <= this.Data.LowThreshold && this.Data.LastQuotation.Sellers[0].Quantity > this.Data.CurrentVolume)
					{
						if (_settings.MaxInverseValue != -1 && this.Data.Max > _settings.MaxInverseValue)
						{
							this.Data.State = DecisionEngineState.DayClosed;
						}
						else
						{
							this.OpenShortPossition(this.Data.CurrentVolume);
							this.Data.StopLoss = _settings.StopLossAt0 ? this.Data.StartValue : this.Data.HighThreshold;
						}
					}
				}

				if (this.Data.State == DecisionEngineState.WaitingNewBlock)
				{
					if (this.Data.LastQuotation.Date.TimeOfDay > _settings.LastOperationStart)
					{
						this.Data.State = DecisionEngineState.DayClosed;
					}
					else if ((this.Data.LastQuotation.Date - this.Data.BlockStart).TotalMinutes >= _settings.BlockTime)
					{
						this.Data.StartValue = 0;
						this.Data.HighThreshold = 0;
						this.Data.LowThreshold = 0;
						this.Data.StopLoss = 0;
						this.Data.State = DecisionEngineState.WaitingOpen;
					}
				}

				if (this.Data.State == DecisionEngineState.OnLongPossition)
				{
					//if (this.Data.LastQuotation.Date.TimeOfDay >= _settings.ForcedCloseTime || this.Data.LastQuotation.Buyers[0].Value <= this.Data.StopLoss || (_settings.MaxProfit != 0 && this.Data.LastQuotation.Buyers[0].Value >= this.Data.MaxProfitThreshold) || (this.Data.LastQuotation.Date - this.Data.BlockStart).TotalMinutes >= _settings.BlockTime)
					if (this.Data.LastQuotation.Date.TimeOfDay >= _settings.ForcedCloseTime || this.Data.LastQuotation.Value <= this.Data.StopLoss || (_settings.MaxProfit != 0 && this.Data.LastQuotation.Value >= this.Data.MaxProfitThreshold) || (this.Data.LastQuotation.Date - this.Data.BlockStart).TotalMinutes >= _settings.BlockTime)
					{
						_operationManager.CloseLongPossition();
						this.Data.CurrentVolume = _volumeCalculator.CalculateNewVolume();
						_dynamicStopLoss.Stop();

						this.Data.State = DecisionEngineState.WaitingNewBlock;
					}
				}

				if (this.Data.State == DecisionEngineState.OnShortPossition)
				{
					//if (this.Data.LastQuotation.Date.TimeOfDay >= _settings.ForcedCloseTime || this.Data.LastQuotation.Sellers[0].Value >= this.Data.StopLoss || (_settings.MaxProfit != 0 && this.Data.LastQuotation.Buyers[0].Value <= this.Data.MaxProfitThreshold) || (this.Data.LastQuotation.Date - this.Data.BlockStart).TotalMinutes >= _settings.BlockTime)
					if (this.Data.LastQuotation.Date.TimeOfDay >= _settings.ForcedCloseTime || this.Data.LastQuotation.Value >= this.Data.StopLoss || (_settings.MaxProfit != 0 && this.Data.LastQuotation.Value <= this.Data.MaxProfitThreshold) || (this.Data.LastQuotation.Date - this.Data.BlockStart).TotalMinutes >= _settings.BlockTime)
					{
						_operationManager.CloseShortPossition();
						this.Data.CurrentVolume = _volumeCalculator.CalculateNewVolume();
						_dynamicStopLoss.Stop();

						this.Data.State = DecisionEngineState.WaitingNewBlock;
					}
				}

				if (this.Data.State == DecisionEngineState.DayClosed)
				{
					if (this.Data.LastQuotation.Date.TimeOfDay < _settings.LastOperationStart && this.Data.ProcessedDay != this.Data.LastQuotation.Date.Date)
					{
						this.OnRealtimeNotification(string.Format("Starting a new day. Old data: StartValue = {0} - HighThreshold = {1} - LowThreshold = {2} - StopLoss = {3}", this.Data.StartValue, this.Data.HighThreshold, this.Data.LowThreshold, this.Data.StopLoss));
						this.Data.State = DecisionEngineState.WaitingOpen;
						this.Data.StartValue = 0;
						this.Data.HighThreshold = 0;
						this.Data.LowThreshold = 0;
						this.Data.StopLoss = 0;
					}
				}
			}
		}

		private void CalculateMaxMin()
		{
			if ((this.Data.State != DecisionEngineState.AccessorDisconnected && this.Data.State != DecisionEngineState.DayClosed && this.Data.State != DecisionEngineState.WaitingOpen) || (this.Data.State == DecisionEngineState.AccessorDisconnected && this.Data.PreviousState != DecisionEngineState.DayClosed && this.Data.PreviousState != DecisionEngineState.WaitingOpen))
			{
				this.Data.Max = Math.Max(this.Data.Max, this.Data.LastQuotation.Value);
				this.Data.Min = Math.Min(this.Data.Min, this.Data.LastQuotation.Value);	
			}
			else
			{
				this.Data.Max = decimal.MinValue;
				this.Data.Min = decimal.MaxValue;	
			}
		}

		private void OpenLongPossition(int volume)
		{
			_operationManager.OpenLongPossition(volume);
			_dynamicStopLoss.Start(true);

			this.Data.MaxProfitThreshold = _settings.MaxProfit != 0 ? _operationManager.CurrentOperation.BuyPrice + _settings.MaxProfit : 0;
			this.Data.State = DecisionEngineState.OnLongPossition;
		}

		private void OpenShortPossition(int volume)
		{
			_operationManager.OpenShortPossition(volume);
			_dynamicStopLoss.Start(false);
			
			this.Data.MaxProfitThreshold = _settings.MaxProfit != 0 ? _operationManager.CurrentOperation.SellPrice - _settings.MaxProfit : 0;
			this.Data.State = DecisionEngineState.OnShortPossition;
		}

		#endregion

		#region IDisposable

		protected bool Disposed { get; private set; }

		private void Dispose(bool disposing)
		{
			if (!this.Disposed)
			{
				this.InternalDispose(disposing);
			}

			this.Disposed = true;
		}

		protected virtual void InternalDispose(bool disposing)
		{
			if (disposing)
			{
				_quotationSource.CollectionChanged -= QuotationSource_CollectionChanged;
				_dynamicStopLoss.PropertyChanged -= _dynamicStopLoss_PropertyChanged; // Not needed because it's an internal object but anyway it's a good practice.

				if (_accessorInformation != null)
				{
					_accessorInformation.PropertyChanged -= AccessorInformation_PropertyChanged;
				}

				if (_dynamicStopLoss != null) 
				{
					_dynamicStopLoss.Dispose();
				}
			}

			// Dispose here only unmanaged objects 
			// Don’t use managed objects here because maybe 
			// they have been finalized already
		}

		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		~DecisionEngine()
		{
			this.Dispose(false);
		}

		#endregion

	}
}
